package com.wbl.train.business.mq.producer;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.date.DateTime;
import cn.hutool.core.util.ObjectUtil;
import com.alibaba.csp.sentinel.annotation.SentinelResource;
import com.alibaba.csp.sentinel.slots.block.BlockException;
import com.alibaba.fastjson.JSON;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.wbl.train.business.service.ConfirmOrderService;
import com.wbl.train.business.service.DailySkTokenService;
import com.wbl.train.business.service.DailyTrainService;
import com.wbl.train.business.service.DailyTrainTicketService;
import com.wbl.train.common.business.dto.ConfirmOrderDto;
import com.wbl.train.common.business.dto.ConfirmOrderTicket;
import com.wbl.train.common.business.entity.ConfirmOrderEntity;
import com.wbl.train.common.business.entity.DailyTrainEntity;
import com.wbl.train.common.business.entity.DailyTrainTicketEntity;
import com.wbl.train.common.business.enums.ConfirmOrderStatusEnum;
import com.wbl.train.common.business.enums.RocketMQTopicEnum;
import com.wbl.train.common.business.enums.SeatTypeEnum;
import com.wbl.train.common.context.LoginMemberContext;
import com.wbl.train.common.exception.ApplicationException;
import com.wbl.train.common.result.ResultCode;
import com.wbl.train.common.utils.SnowUtil;
import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.rocketmq.spring.core.RocketMQTemplate;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service;

import java.time.LocalDate;
import java.util.List;
import java.util.Objects;

/**
 * Author: 王博纶
 * Date: 2024/5/27 13:34
 * FileName: BeforeConfirmOrder
 * Description: 执行真正的抢票逻辑之前的业务
 */
@AllArgsConstructor
@Slf4j
@Service
public class BeforeConfirmOrder{
    private final DailySkTokenService dailySkTokenService;
    private final RocketMQTemplate rocketMQTemplate;
    private final DailyTrainService dailyTrainService;
    private final DailyTrainTicketService dailyTrainTicketService;
    @Lazy
    private final ConfirmOrderService confirmOrderService;

    /**
     *  出票之前的操作方法 --- 将出票的请求放入MQ中，异步处理
     * @param confirmOrderDto 确认订单信息Dto
     */
    @SentinelResource(value = "beforeDoConfirm", blockHandler = "beforeDoConfirmBlock")
    public void beforeDoConfirm(ConfirmOrderDto confirmOrderDto){
        // 获取当前登录会员的id
        Long memberId = LoginMemberContext.getId();
        confirmOrderDto.setMemberId(memberId);
        // 校验令牌的数量
        boolean validSKToken = dailySkTokenService.validSKToken(confirmOrderDto.getDate(),confirmOrderDto.getTrainCode(),memberId,1);
        if(validSKToken){
            // 校验通过
            log.info("令牌校验通过");
        }else{
            // 校验不通过
            log.error("令牌校验不通过");
            throw new ApplicationException(ResultCode.VALID_SK_TOKEN,"票已卖完");
        }

        // 在发送MQ消息之前先保存一次订单数据
        LocalDate date = confirmOrderDto.getDate();
        String trainCode = confirmOrderDto.getTrainCode();
        String start = confirmOrderDto.getStart();
        String end = confirmOrderDto.getEnd();
        List<ConfirmOrderTicket> confirmOrderTickets = confirmOrderDto.getConfirmOrderTickets();
        String seatTypeCode = confirmOrderTickets.get(0).getSeatTypeCode();
        // 获取当前的时间 --- 用于创建和修改的时候的时间填充
        DateTime now = DateTime.now();
        // 1.业务数据校验，车次是否存在，余票是否存在，车次是否在有效期内，tickets条数>0，同乘客同车次是否已买过票
        checkBusinessData(date, trainCode, start, end,seatTypeCode);
        // 2.保存确认订单，状态为 "初始"
        ConfirmOrderEntity confirmOrderEntity = confirmOrderSave(confirmOrderDto, now);

        log.info("准备发送MQ消息，等待出票");
        // 将请求的对象转换为一个json字符串
        String json = JSON.toJSONString(confirmOrderDto);
        log.info("排队购票，开始发送mq消息：{}",json);
        // 使用rocketMQTemplate.convertAndSend发送消息，设置主题和请求参数
        // todo 需要新增一个订单MQ的dto类（维度：日期，车次），只需要传递该dto类，MQ处理的时候直接从数据库中查询对应车次和日期的订单进行出票即可
        rocketMQTemplate.convertAndSend(RocketMQTopicEnum.CONFIRM_ORDER.getCode(),json);
        log.info("排队购票，mq消息发送完");
    }

    /**
     * 业务数据校验，车次是否存在，余票是否存在，车次是否在有效期内，tickets条数>0，同乘客同车次是否已买过票
     * @param date 车次的日期
     * @param trainCode 车次的编号
     * @param start 起始站
     * @param end 终点站
     * @param seatTypeCode 座位类型
     */
    private void checkBusinessData(LocalDate date, String trainCode, String start, String end, String seatTypeCode) {
        // 车次是否存在，车次是否在有效期内
        LambdaQueryWrapper<DailyTrainEntity> wrapper = new LambdaQueryWrapper<DailyTrainEntity>()
                .eq(DailyTrainEntity::getCode, trainCode)
                .eq(DailyTrainEntity::getDate, date);
        DailyTrainEntity train = dailyTrainService.getOne(wrapper);
        if(ObjectUtil.isNull(train)){
            throw new ApplicationException(ResultCode.DATA_NOT_EXIST,"当前车次不存在");
        }
        //同乘客同车次是否已买过票



        // 余票是否存在
        DailyTrainTicketEntity trainTicket = dailyTrainTicketService.getTrainTicketByUniqueKey(date, trainCode, start, end);
        if(ObjectUtil.isNull(trainTicket)){
            throw new ApplicationException(ResultCode.DATA_NOT_EXIST,"当前余票不存在");
        }
        // tickets条数 > 0
        // 获取座位类型的中文名称
        String seatName = Objects.requireNonNull(SeatTypeEnum.getEnumByCode(seatTypeCode)).getDesc();
        // java17的switch增强写法 --- 使用 case xxx -> {} 的方式，直接在{}里面写逻辑，省略break语句
        switch(seatName){
            case "一等座" -> getCountResidueTicket(trainTicket.getYdz(),"一等座的余票数量不足",true);
            case "二等座" -> getCountResidueTicket(trainTicket.getEdz(),"二等座的余票数量不足",true);
            case "软卧" -> getCountResidueTicket(trainTicket.getRw(),"软卧的余票数量不足",true);
            case "硬卧" -> getCountResidueTicket(trainTicket.getYw(),"硬卧的余票数量不足",true);
        }
    }

    /**
     * 保存确认订单，状态为 "初始"
     * @param confirmOrderDto 确认订单的dto
     * @param now 当前时间
     * @return 将保存的订单返回出去
     */
    private ConfirmOrderEntity confirmOrderSave(ConfirmOrderDto confirmOrderDto, DateTime now) {
        ConfirmOrderEntity confirmOrderEntity = BeanUtil.copyProperties(confirmOrderDto, ConfirmOrderEntity.class);
        confirmOrderEntity
                .setId(SnowUtil.getSnowflakeNextId())
                .setMemberId(confirmOrderDto.getMemberId())
                .setStatus(ConfirmOrderStatusEnum.INIT.getCode())
                .setTickets(JSON.toJSONString(confirmOrderDto.confirmOrderTickets))
                .setCreateTime(now)
                .setUpdateTime(now);
        confirmOrderService.save(confirmOrderEntity);
        return confirmOrderEntity;
    }

    /**
     * 获取余票的数量
     * @param ticket 座位类型的数量
     * @param infoMessage 报错的信息提示
     * @param isEqual 比较符是否需要加 "=" true: "<=", false: "<"
     * @return 当前座位的余票数量
     */
    private static int getCountResidueTicket(int ticket, String infoMessage, boolean isEqual) {
        int countResidueTicket = ticket - 1;
        if(isEqual){
            if(ticket <= 0){
                throw new ApplicationException(ResultCode.INSUFFICIENT_REMAINING_TICKETS,infoMessage);
            }
            return ticket;
        }
        if(countResidueTicket < 0){
            throw new ApplicationException(ResultCode.INSUFFICIENT_REMAINING_TICKETS,infoMessage);
        }
        return countResidueTicket;
    }

    /**
     * beforeDoConfirmBlock资源降级的处理方法
     * @param confirmOrderDto 确认订单信息Dto
     * @param e 异常
     */
    private void beforeDoConfirmBlock(ConfirmOrderDto confirmOrderDto, BlockException e){
        log.info("beforeDoConfirm资源进行限流降级处理");
    }
}
